%%%-------------------------------------------------------------------
%%% @author Rain
%%% @doc 帧同步匹配进程
%%% @end
%%%-------------------------------------------------------------------
-module(ls_match_svr).
-feature(maybe_expr,enable).

-include("elock_step.hrl").
-behaviour(gen_server).

-export([start_link/1]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
         code_change/3]).

-record(state, {}).

%%%===================================================================
%%% Spawning and gen_server implementation
%%%===================================================================

-type match_opt() ::
    {'match_count', MatchCount :: integer()}.

-spec start_link(Options :: [match_opt()]) -> gen_server:start_ret().
start_link(Options) ->
    gen_server:start_link(?MODULE, Options, []).

init(Options) ->
    MatchCount = proplists:get_value(match_count, Options),
    set_dict_var(?LS_MATCH_KEY_MATCH_COUNT, MatchCount),
    {ok, #state{}}.

handle_call(_Request, _From, State = #state{}) ->
    {reply, ok, State}.

handle_cast(_Request, State = #state{}) ->
    {noreply, State}.

%% 玩家请求匹配
handle_info({c_match, RoleId}, State = #state{}) ->
    client_call_match_svr_match(RoleId),
    {noreply, State};

handle_info(_Info, State = #state{}) ->
    {noreply, State}.

terminate(_Reason, _State = #state{}) ->
    ok.

code_change(_OldVsn, State = #state{}, _Extra) ->
    {ok, State}.

%%%===================================================================
%%% common api 通用api方法
%%%===================================================================

dict_var(Atom) ->
    dict_var(Atom, undefined).
dict_var(Atom, Def) ->
    case get({'$ls_match_var', Atom}) of
        undefined -> Def;
        V -> V
    end.

set_dict_var(Atom, V) ->
    put({'$ls_match_var', Atom}, V).

erase_dict_var(Atom) ->
    erlang:erase({'$ls_match_var', Atom}).

%%%===================================================================
%%% handle处理相关的方法
%%%===================================================================

%% @doc 客户端通知匹配进程进行匹配
client_call_match_svr_match(RoleId) ->
    %% TODO 这里要判断是否在ets里面存在了match_id, 如果存在了需要找到对应的进程通知把他给移除掉。
    WaitMatchRoles = dict_var(?LS_MATCH_KEY_WAIT_MATCH_ROLES, []),
    IsInMatchQueue = lists:member(RoleId, WaitMatchRoles),
    maybe
        false ?= IsInMatchQueue,
        WaitMatchRoles1 = [RoleId | WaitMatchRoles],
        set_dict_var(?LS_MATCH_KEY_WAIT_MATCH_ROLES, WaitMatchRoles1),
        MatchCount = dict_var(?LS_MATCH_KEY_MATCH_COUNT),
        elock_step_behaviour:call_client_match(RoleId, ok),
        true ?= length(WaitMatchRoles1) >= MatchCount, % 人数足够，申请一个房间

        %% 申请一个房间
        RoomParams = #{role_ids => WaitMatchRoles1},
        % 就算失败也清空排队的队列，让他们重新排
        erase_dict_var(?LS_MATCH_KEY_WAIT_MATCH_ROLES),
        case ls_util:open_room(RoomParams) of
            {ok, RoomPid} ->
                {ok, MatchId} = ls_util:new_match_id(),
                ls_util:set_ls_match_room(
                    #ls_match_room{match_id = MatchId, room_pid = RoomPid}),
                [ls_util:set_ls_role_match(#ls_role_match{match_id = MatchId, role_id = AccRoleId})
                    || AccRoleId <- WaitMatchRoles1],
                match_svr_call_client_succ(WaitMatchRoles1, MatchId),
                ok;
            _Error ->
                match_svr_call_client_fail(WaitMatchRoles1)
        end,
        % 等待进入房间的确认消息，如果超时要通知所有玩家退出房间，重新匹配
        ok
    else
        true when IsInMatchQueue ->
            %% 协议通知客户端匹配失败，已经在匹配队列里了。
            elock_step_behaviour:call_client_match(RoleId, already_in_match_queue),
            ok;
        _ ->
            pass
    end.


%% @doc 匹配进程通知客户端成功匹配，客户端切换场景
match_svr_call_client_succ(WaitMatchRoles, MatchId) ->
    Fun =
        fun(RoleId) ->
            %% 协议通知客户端匹配成功，此后客户端请求切换场景
            elock_step_behaviour:call_client_match_succ(RoleId, MatchId),
            ok

        end,
    lists:foreach(Fun, WaitMatchRoles).

%% @doc 匹配进程通知客户端匹配失败，需要重新匹配
match_svr_call_client_fail(WaitMatchRoles) ->
    Fun =
        fun(RoleId) ->
            % 协议通知客户端匹配失败，此后客户端重启手动点击匹配，或程序自动匹配
            elock_step_behaviour:call_client_match_fail(RoleId),
            ok
        end,
    lists:foreach(Fun, WaitMatchRoles).